/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2024-2024.
 * Description: vnotify dev operation
 * Author: wanglinhui <wanglinhui@huawei.com>
 * Create: 2024-04-12
 */

#include "dfx.h"
#include "vnotify.h"

#include <linux/miscdevice.h>
#include <linux/hpvisor.h>
#include <linux/module.h>

static const struct vnotify_ioctl_operations *vnotify_oper;

struct ioctl_handle_t {
	unsigned int ioctl;
	long (*handle)(struct hpvisor_ioctl_arg *arg);
	int allow_mode;
};

void set_vnotify_operations(const struct vnotify_ioctl_operations *oper)
{
	vnotify_oper = oper;
}

static long handle_check(struct hpvisor_ioctl_arg *arg)
{
	if (!vnotify_oper->check)
		return -EINVAL;
	return vnotify_oper->check();
}

static long handle_query(struct hpvisor_ioctl_arg *arg)
{
	if (!vnotify_oper->query)
		return -EINVAL;
	return vnotify_oper->query(arg->arg1);
}

static long handle_ctrl_attach(struct hpvisor_ioctl_arg *arg)
{
	if (!vnotify_oper->ctrl_attach)
		return -EINVAL;
	return vnotify_oper->ctrl_attach();
}

static long handle_ctrl_detach(struct hpvisor_ioctl_arg *arg)
{
	if (!vnotify_oper->ctrl_detach)
		return -EINVAL;
	return vnotify_oper->ctrl_detach(arg->arg1);
}

static long handle_ctrl_send(struct hpvisor_ioctl_arg *arg)
{
	if (!vnotify_oper->ctrl_send)
		return -EINVAL;
	return vnotify_oper->ctrl_send(arg->arg1);
}

static long handle_guest_attach(struct hpvisor_ioctl_arg *arg)
{
	if (!vnotify_oper->guest_attach)
		return -EINVAL;
	return vnotify_oper->guest_attach(arg->arg1, arg->arg2);
}

static long handle_guest_detach(struct hpvisor_ioctl_arg *arg)
{
	if (!vnotify_oper->guest_detach)
		return -EINVAL;
	return vnotify_oper->guest_detach(arg->arg1);
}

static long handle_host_attach(struct hpvisor_ioctl_arg *arg)
{
	if (!vnotify_oper->host_attach)
		return -EINVAL;
	return vnotify_oper->host_attach(arg->arg1, arg->arg2, arg->arg3);
}

static long handle_host_send(struct hpvisor_ioctl_arg *arg)
{
	if (!vnotify_oper->host_send)
		return -EINVAL;
	return vnotify_oper->host_send(arg->arg1);
}

static long handle_host_wake(struct hpvisor_ioctl_arg *arg)
{
	if (!vnotify_oper->host_wake)
		return -EINVAL;
	return vnotify_oper->host_wake(arg->arg1);
}

static long handle_host_find(struct hpvisor_ioctl_arg *arg)
{
	if (!vnotify_oper->host_find)
		return -EINVAL;
	return vnotify_oper->host_find(arg->arg1);
}

static long handle_host_detach(struct hpvisor_ioctl_arg *arg)
{
	if (!vnotify_oper->host_detach)
		return -EINVAL;
	return vnotify_oper->host_detach(arg->arg1, (int)arg->arg2, (int)arg->arg3);
}

static long handle_get_strides_mem_size(struct hpvisor_ioctl_arg *arg)
{
	if (!vnotify_oper->get_strides_mem_size)
		return -EINVAL;
	return vnotify_oper->get_strides_mem_size();
}

static long handle_print_record(struct hpvisor_ioctl_arg *arg)
{
	if (!vnotify_oper->print_record)
		return -EINVAL;
	return vnotify_oper->print_record(arg->arg1);
}

static long handle_get_chn_xid(struct hpvisor_ioctl_arg *arg)
{
	if (!vnotify_oper->get_chn_xid)
		return -EINVAL;
	return vnotify_oper->get_chn_xid(arg->arg1, arg->arg2);
}

static long handle_get_vnotify_setting(struct hpvisor_ioctl_arg *arg)
{
	if (!vnotify_oper->get_vnotify_setting)
		return -EINVAL;
	return vnotify_oper->get_vnotify_setting(arg->arg2);
}

static int vnotify_ioctl_mem_init(struct hpvisor_ioctl_arg *arg)
{
	return um_ioctl_vnotify_mem_init_stride(arg->arg2);
}

static struct ioctl_handle_t g_ioctl_handles[] = {
	{HPVISOR_VNOTIFY_CHECK, handle_check, VM_MODE},
	{HPVISOR_VNOTIFY_QUERY, handle_query, VM_MODE | UM_MODE},
	{HPVISOR_VNOTIFY_CTRL_ATTACH, handle_ctrl_attach, VM_MODE},
	{HPVISOR_VNOTIFY_CTRL_DETACH, handle_ctrl_detach, VM_MODE},
	{HPVISOR_VNOTIFY_CTRL_SEND, handle_ctrl_send, VM_MODE},
	{HPVISOR_VNOTIFY_GUEST_ATTACH, handle_guest_attach, VM_MODE},
	{HPVISOR_VNOTIFY_GUEST_DETACH, handle_guest_detach, VM_MODE},
	{HPVISOR_VNOTIFY_HOST_SEND, handle_host_send, VM_MODE},
	{HPVISOR_VNOTIFY_HOST_WAKE, handle_host_wake, VM_MODE},
	{HPVISOR_VNOTIFY_HOST_FIND, handle_host_find, VM_MODE | UM_MODE},
	{HPVISOR_VNOTIFY_HOST_ATTACH, handle_host_attach, VM_MODE | UM_MODE},
	{HPVISOR_VNOTIFY_HOST_DETACH, handle_host_detach, VM_MODE | UM_MODE},
	{HPVISOR_VNOTIFY_PRINT_RECORD, handle_print_record, VM_MODE},
	{HPVISOR_VNOTIFY_GET_STRIDES_MEM_SIZE, handle_get_strides_mem_size, VM_MODE},
	{HPVISOR_VNOTIFY_GET_CHN_XID, handle_get_chn_xid, VM_MODE | UM_MODE},
	{HPVISOR_VNOTIFY_GET_SETTING, handle_get_vnotify_setting, VM_MODE},
};

static struct ioctl_handle_t *vnotify_get_ioctl_handle(unsigned int ioctl,
	unsigned int vnotify_mode)
{
	int i, num;

	num = sizeof(g_ioctl_handles) / sizeof(struct ioctl_handle_t);
	for (i = 0; i < num; i++) {
		if (ioctl == g_ioctl_handles[i].ioctl) {
			if ((g_ioctl_handles[i].allow_mode & vnotify_mode) != 0)
				return &g_ioctl_handles[i];
			hpvisor_err("operation is not supported in the current mode\n");
			return NULL;
		}
	}
	return NULL;
}

static long vnotify_dev_ioctl(struct file *filp, unsigned int ioctl, unsigned long argp)
{
	long ret;
	struct hpvisor_ioctl_arg arg;
	struct vnotify_data_t *vdata_t;
	struct ioctl_handle_t *ioctl_handle = NULL;

	if (likely(argp > 0)) {
		if (unlikely(!access_ok((void __user *)argp, sizeof(arg))))
			return -EFAULT;

		if (copy_from_user(&arg, (void __user *)argp, sizeof(arg)))
			return -EFAULT;
	}

	if (ioctl == UM_VNOTIFY_MEM_INIT)
		return vnotify_ioctl_mem_init(&arg);

	vdata_t = get_vdata_t();
	if (unlikely(!vdata_t)) {
		hpvisor_err("vdata_t is null\n");
		return -ENODATA;
	}

	if (unlikely(!vdata_t->get_vnotify()))
		return -ENODATA;

	ret = vdata_t->task_first_call();
	if (ret != 0)
		goto out;

	if ((vdata_t->mode == VM_MODE) && !vdata_t->check_task_perm()) {
		ret = -EPERM;
		goto out;
	}

	ioctl_handle = vnotify_get_ioctl_handle(ioctl, vdata_t->mode);
	if (likely(ioctl_handle != NULL))
		ret = ioctl_handle->handle(&arg);
	else
		ret = -EINVAL;
out:
	vdata_t->put_vnotify();
	return ret;
}

static struct file_operations vnotify_chardev_ops = {
	.owner			= THIS_MODULE,
	.llseek			= noop_llseek,
	.unlocked_ioctl		= vnotify_dev_ioctl,
	.mmap			= NULL,
};

struct file_operations *get_vnotify_cdev_ops(void)
{
	return &vnotify_chardev_ops;
}

static struct miscdevice vnotify_dev = {
	.minor	= MISC_DYNAMIC_MINOR,
	.name	= "hpvisor",
	.fops	= &vnotify_chardev_ops,
};

static int vnotify_init(void)
{
	int ret;

	ret = misc_register(&vnotify_dev);
	if (ret)
		hpvisor_err("misc device register failed\n");
	return ret;
}

module_init(vnotify_init);
